package p2p;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;

/** Class that contains global variables and functions and
 * some constants to parametrize the program.
 * @author Bruno
 */
public class Global {

	/** Program name. */
	final static String name = "P2P SDIS";

	// Program version
	final static int version_major = 0;
	final static int version_minor = 0;
	final static int version_revision = 1;

	/** Chunk data size in bytes. */
	final static int chunkSize = 1024;
	/** (Max possible + 1) value for search ID's. */
	final static double searchIDMaxValue = 10000000000.0;
	/** Interval (in miliseconds) between each refresh of the "Files" table values in frmP2P. */
	final static int filesTableRefreshInterval = 1000;

	/** Multicast address. */
	static String multicastAddr = "224.0.2.10";
	/** Multicast IP address as an <i>InetAddress</i> object. */
	static InetAddress multicastInetAddr = null;
	/** Multicast control port. */
	static int controlPort = 8967;
	/** Multicast data port. */
	static int dataPort = 8966;
	/** Algorithm used to calculate the checksum of a file. */
	private static String messageDigestAlgorithmName = "SHA-256";

	/** Socket associated with the multicast control port. */
	static MulticastSocket controlSocket = null;
	/** Socket associated with the multicast data port. */
	static MulticastSocket dataSocket = null;

	// Forms
	static FrmP2P frmP2P = null;

	// Threads
	static ControlPortThread controlPortThread = new ControlPortThread();
	static DataPortThread dataPortThread = new DataPortThread();
	static UpdateFilesTableThread updateFilesTableThread = new UpdateFilesTableThread();

	/** Get the version of the program as a string.
	 * @return String with the version of the program.
	 */
	static String getVersion() {
		return version_major + "." + version_minor + "." + version_revision;
	}

	/** Get the full name of the program, containing it's version.
	 * @return Program's full name.
	 */
	static String getFullName() {
		return name + " " + getVersion();
	}

	/** Converts the size of a file to a string easier to read,
	 * showing the size in B, KB, MB or GB when appropriate.
	 * @param size Size to convert.
	 * @return String with the size in human-readable format.
	 */
	static String sizeToHumanReadable(long size) {
		if (size < 1024) // < 1 KB
		{
			return size + " B";
		} else {
			double aux = (double) size;
			DecimalFormat format = new DecimalFormat("0.0"); // format doubles (aux variable) to have 1 decimal digit
			aux /= 1024;
			if (aux < 1024) // < 1 MB
			{
				return format.format(aux) + " KB";
			} else {
				aux /= 1024;
				if (aux < 1024) // < 1 GB
				{
					return format.format(aux) + " MB";
				} else {
					return format.format(aux / 1024.0) + " GB";
				}
			}
		}
	}

	/** Sets the algorithm used to calculate a file's checksum and checks its existence.
	 * @param algorithm Algorithm used to calculate a file's checksum
	 * @return <b>true</b> if the algorithm exists.
	 */
	static boolean setMessageDigestAlgorithm(String algorithm) {
		// Try to get the algorithm just to check if it exists.
		try {
			MessageDigest.getInstance(algorithm);
			return true;
		} catch (NoSuchAlgorithmException ex) {
			return false;
		}
	}

	/** Calculates the checksum of a file using the <i>messageDigestAlgorithm</i> algorithm.
	 * @param filename The name of file to calculate the checksum.
	 * @throws FileNotFoundException If the specified <i>filename</i> file doesn't exist.
	 * @throws NoSuchAlgorithmException If the specified <i>messageDigestAlgorithm</i> doesn't exist.
	 * @throws IOException If some sort of I/O error occurred.
	 * @return String with the checksum of the file.
	 */
	static String calculateCheckSum(String filename) throws FileNotFoundException, NoSuchAlgorithmException, IOException {
		FileInputStream fis = new FileInputStream(filename);
		return calculateCheckSum(fis);
	}

	/** Calculates the checksum of a file using the <i>messageDigestAlgorithm</i> algorithm.
	 *
	 * @param file The input stream pointing to the file that will be checksum'ed.
	 * @throws NoSuchAlgorithmException If the specified <i>messageDigestAlgorithm</i> doesn't exist.
	 * @throws IOException If some sort of I/O error occurred.
	 * @return String with the checksum of the file.
	 */
	static String calculateCheckSum(FileInputStream file) throws NoSuchAlgorithmException, IOException {
		MessageDigest md = MessageDigest.getInstance(messageDigestAlgorithmName);
		byte[] dataBytes = new byte[1024];
		int nread = 0;

		while ((nread = file.read(dataBytes)) != -1) {
			md.update(dataBytes, 0, nread);
		}
		byte[] mdbytes = md.digest();

		return byteToHexCheckSum(mdbytes);
	}

	/** Convert a checksum from hex to byte format.
	 * @param checksum Checksum in hex format.
	 * @return Checksum in byte format.
	 */
	static byte[] hexToByteCheckSum(String checksum) {
		int size = checksum.length() / 2;
		byte[] mdbytes = new byte[size];
		String str = checksum.toLowerCase();

		int j = 0;
		int high, low;
		for(int i = 0; i < size; i++) {
			high = Character.digit(checksum.charAt(j++), 16);
			low = Character.digit(checksum.charAt(j++), 16);
			mdbytes[i] = (byte)((high << 4) + low);
		}

		return mdbytes;
	}

	/** Convert a checksum from byte to hex format.
	 * @param checksum Checksum in byte format.
	 * @return Checksum in hex format.
	 */
	static String byteToHexCheckSum(byte[] mdbytes) {
		// convert the byte to hex format
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < mdbytes.length; i++) {
			sb.append(Integer.toString((mdbytes[i] & 0xff) + 0x100, 16).substring(1));
		}

		return sb.toString();
	}

	/** Generate a random search identifier used when sending SEARCH and FOUND messages to the network.
	 * @return Random search identifier.
	 */
	static String generateSearchID() {
		return "id" + (long)(Math.random() * searchIDMaxValue);
	}
}
